#ifndef __TDP2_ZIPFILE_HPP__
#define __TDP2_ZIPFILE_HPP__

#include <stdio.h>
#include "tdp2.hpp"
#include "tdp2_vector.hpp"

namespace TDP
{
	namespace ZipFile
	{
		const UInt32 CodeDirEnd_k = 0x06054b50;
		const UInt32 CodeLocalFile_k = 0x04034b50;
		const UInt32 CodeCentralEntry_k = 0x02014b50;

		const UInt16 VersionExtractDeflate_k = 20;
		const UInt16 VersionExtractStore_k = 10;

		const UInt16 MethodDeflate_k = 8;
		const UInt16 MethodStore_k = 0;

		inline UInt32 DecodeLong(const UInt8 *buf)
		{
			return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
		}

		inline UInt16 DecodeShort(const UInt8 *buf)
		{
			return buf[0] | (buf[1] << 8);
		}

		struct ArchiveEntry
		{
			UInt16 versionMade;
			UInt16 versionExtract;

			UInt16 bitFlag;
			UInt16 method;
		
			UInt16 modTime;
			UInt16 modDate;

			UInt32 crc32;

			UInt32 compressedSize;
			UInt32 uncompressedSize;

			UInt16 internalAttributes;
			UInt32  externalAttributes;

			UInt32 localHeaderOffset;

			Containers::Vector<char> fileName;

			inline explicit ArchiveEntry(const RDX::ObjectManagement::Allocator &alloc)
				: fileName(alloc)
			{
			}

		    inline bool Read(RDX::IO::IFileStream *f)
		    {
				UInt8 buf[46];
				if(f->ReadBytes(buf, 46) != 46)
					return false;
				if(DecodeLong(buf+0) != CodeCentralEntry_k)
					return false;
				this->versionMade = DecodeShort(buf+4);
				this->versionExtract = DecodeShort(buf+6);

				this->bitFlag = DecodeShort(buf+8);
				this->method = DecodeShort(buf+10);

				this->modTime = DecodeShort(buf+12);
				this->modDate = DecodeShort(buf+14);

				this->crc32 = DecodeLong(buf+16);

				this->compressedSize = DecodeLong(buf+20);
				this->uncompressedSize = DecodeLong(buf+24);

				if(!this->fileName.TryResize(DecodeShort(buf+28)))
					return false;

				if(DecodeShort(buf+30) != 0 || DecodeShort(buf+32) != 0 || DecodeShort(buf+34) != 0)
				{
					// Comments present, unsupported, bail
					return false;
				}

				this->internalAttributes = DecodeShort(buf+36);
				this->externalAttributes = DecodeLong(buf+38);

				this->localHeaderOffset = DecodeLong(buf+42);
				if(f->ReadBytes(this->fileName, this->fileName.Count()) != this->fileName.Count())
					return false;
				return true;
			}


#define TDP_ZIPFILE_ASSERT(a, b) \
do { \
	UInt32 va = (a);\
	UInt32 vb = (b);\
	if(va != vb)\
		return false;\
} while(false)

			inline bool VerifyLocal(RDX::IO::IFileStream *f) const
			{
				UInt8 buf[30];
				size_t offset = static_cast<size_t>(f->Tell());

				if(f->ReadBytes(buf, 30) != 30)
					return false;

				if(DecodeLong(buf) != CodeLocalFile_k
					|| DecodeShort(buf+4) != this->versionExtract
					|| DecodeShort(buf+6) != this->bitFlag
					|| DecodeShort(buf+8) != this->method
					|| DecodeShort(buf+10) != this->modTime
					|| DecodeShort(buf+12) != this->modDate
					|| DecodeLong(buf+14) != this->crc32
					|| DecodeLong(buf+18) != this->compressedSize
					|| DecodeLong(buf+22) != this->uncompressedSize
					|| DecodeShort(buf+26) != this->fileName.Count()
					|| DecodeShort(buf+28) != 0)
					return false;

				LargeUInt nFileNameBytes = this->fileName.Count();
				const char *fileNameScan = this->fileName;
				while(nFileNameBytes)
				{
					LargeUInt bytesToCheck = 30;
					if(nFileNameBytes < 30)
						bytesToCheck = nFileNameBytes;
					if(f->ReadBytes(buf, bytesToCheck) != bytesToCheck)
						return false;
					if(memcmp(buf, fileNameScan, bytesToCheck))
						return false;
					fileNameScan += bytesToCheck;
					nFileNameBytes -= bytesToCheck;
				}

				return true;
			}

			inline unsigned int LocalHeaderSize() const
			{
				return 30 + this->fileName.Count();
			}
		};

		struct CentralDirEnd
		{
			UInt16 centralDirTotal;
			UInt32 centralDirSize;
			UInt32 centralDirOffset;

		    inline bool Read(RDX::IO::IFileStream *f)
		    {
				UInt8 buf[22];
				if(f->ReadBytes(buf, 22) != 22)
					return false;

				if(DecodeLong(buf) != CodeDirEnd_k)
					return false;
				if(DecodeLong(buf+4) != 0)
					return false;
				this->centralDirTotal = DecodeShort(buf+8);
				if(DecodeShort(buf+10) != this->centralDirTotal)
					return false;
				this->centralDirSize = DecodeLong(buf+12);
				this->centralDirOffset = DecodeLong(buf+16);
				if(DecodeShort(buf+20) != 0)
					return false;
				return true;
			}

			inline static UInt32 DiskSize()
			{
				return 22;
			}
		};
	}
};

#endif
